/*
 * Copyright 2007,2008 Anton Dignoes, Daniel Rampanelli, Martin Palma
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.webduff.locking;

import java.util.UUID;

/**
 * a helper class for ResourceLocks, represents the Locks
 * 
 * @author re
 * 
 */
public class LockObject {
	
	private ResourceLocks theResourceLocks;

    private String fPath;
    
    private String ID;

    /**
     * owner of the lock. shared locks can have multiple owners. is null if
     * no owner is present
     */
    protected String[] fOwner = null;

    /**
     * children of that lock
     */
    protected LockObject[] fChildren = null;

    protected LockObject fParent = null;

    /**
     * weather the lock is exclusive or not. if owner=null the exclusive
     * value doesn't matter
     */
    protected boolean fExclusive = false;
    
    /**
     * 
     * @param path
     *            the path to the locked object
     */
    public LockObject(ResourceLocks resourceLocks, String path) {
        fPath = path;
        ID = UUID.randomUUID().toString();
        
        theResourceLocks = resourceLocks;
        
        theResourceLocks.fLocks.put(path, this);
        theResourceLocks.fLocksByID.put(ID, this);
        
        theResourceLocks.fCleanupCounter++;
    }
    
    public String getID() {
    	return ID;
    }

    /**
     * adds a new owner to a lock
     * 
     * @param owner
     *            string that represents the owner
     * @return true if the owner was added, false otherwise
     */
    public boolean addLockObjectOwner(String owner) {
        if (this.fOwner == null) {
            this.fOwner = new String[1];
        } else {

            int size = this.fOwner.length;
            String[] newLockObjectOwner = new String[size + 1];

            // check if the owner is already here (that should actually not
            // happen)
            for (int i = 0; i < size; i++) {
                if (this.fOwner[i].equals(owner)) {
                    return false;
                }
            }

            System.arraycopy(this.fOwner, 0, newLockObjectOwner, 0, size);
            this.fOwner = newLockObjectOwner;
        }

        this.fOwner[this.fOwner.length - 1] = owner;
        return true;
    }

    /**
     * tries to remove the owner from the lock
     * 
     * @param owner
     *            string that represents the owner
     */
    public void removeLockObjectOwner(String owner) {
        if (this.fOwner != null) {
            int size = this.fOwner.length;
            for (int i = 0; i < size; i++) {
                // check every owner if it is the requested one
                if (this.fOwner[i].equals(owner)) {
                    // remove the owner
                    String[] newLockObjectOwner = new String[size - 1];
                    for (int i2 = 0; i2 < (size - 1); i2++) {
                        if (i2 < i) {
                            newLockObjectOwner[i2] = this.fOwner[i2];
                        } else {
                            newLockObjectOwner[i2] = this.fOwner[i2 + 1];
                        }
                    }
                    this.fOwner = newLockObjectOwner;

                }
            }
            if (this.fOwner.length == 0) {
                this.fOwner = null;
            }
        }
    }

    /**
     * adds a new child lock to this lock
     * 
     * @param newChild new child
     */
    public void addChild(LockObject newChild) {
        if (this.fChildren == null) {
            this.fChildren = new LockObject[0];
        }
        int size = this.fChildren.length;
        LockObject[] newChildren = new LockObject[size + 1];
        System.arraycopy(this.fChildren, 0, newChildren, 0, size);
        newChildren[size] = newChild;
        this.fChildren = newChildren;
    }

    /**
     * deletes this Lock object. assumes that it has no children and no
     * owners (does not check this itself)
     * 
     */
    public void removeLockObject() {
        if (this != theResourceLocks.fRoot) {
            // removing from tree
            int size = this.fParent.fChildren.length;
            for (int i = 0; i < size; i++) {
                if (this.fParent.fChildren[i].equals(this)) {
                    LockObject[] newChildren = new LockObject[size - 1];
                    for (int i2 = 0; i2 < (size - 1); i2++) {
                        if (i2 < i) {
                            newChildren[i2] = this.fParent.fChildren[i2];
                        } else {
                            newChildren[i2] = this.fParent.fChildren[i2 + 1];
                        }
                    }
                    if (newChildren.length != 0) {
                        this.fParent.fChildren = newChildren;
                    } else {
                        this.fParent.fChildren = null;
                    }
                    break;
                }
            }

            // removing from hashtable
            theResourceLocks.fLocksByID.remove(getID());
            theResourceLocks.fLocks.remove(getPath());

            // now the garbage collector has some work to do
        }
    }

    /**
     * checks if a lock of the given exclusivity can be placed, only
     * considering cildren up to "depth"
     * 
     * @param exclusive
     *            wheather the new lock should be exclusive
     * @param depth
     *            the depth to which should be checked
     * @return true if the lock can be placed
     */
    public boolean checkLocks(boolean exclusive, int depth) {
        if (checkParents(exclusive) && checkChildren(exclusive, depth)) {
//        	if(System.currentTimeMillis() <= extinctionTime) {
//        		return true;
//        	}
        	return true;
        }
        return false;
    }

    /**
     * helper of checkLocks(). looks if the parents are locked
     * 
     * @param exclusive
     *            wheather the new lock should be exclusive
     * @return true if no locks at the parent path are forbidding a new lock
     */
    private boolean checkParents(boolean exclusive) {
        if (this.fPath.equals("/")) {
            return true;
        } else {
            if (this.fOwner == null) {
                // no owner, checking parents
                return this.fParent != null && this.fParent.checkParents(exclusive);
            } else {
                // there already is a owner
                return !(this.fExclusive || exclusive) && this.fParent.checkParents(exclusive);
            }
        }
    }

    /**
     * helper of checkLocks(). looks if the children are locked
     * 
     * @param exclusive
     *            wheather the new lock should be exclusive
     * @return true if no locks at the children paths are forbidding a new
     *         lock
     * @param depth depth
     */
    private boolean checkChildren(boolean exclusive, int depth) {
        if (this.fChildren == null) {
            // a file

            return this.fOwner == null || !(this.fExclusive || exclusive);
        } else {
            // a folder

            if (this.fOwner == null) {
                // no owner, checking children

                if (depth != 0) {
                    boolean canLock = true;
                    int limit = this.fChildren.length;
                    for (int i = 0; i < limit; i++) {
                        if (!this.fChildren[i].checkChildren(exclusive,
                                depth - 1)) {
                            canLock = false;
                        }
                    }
                    return canLock;
                } else {
                    // depth == 0 -> we don't care for children
                    return true;
                }
            } else {
                // there already is a owner
                return !(this.fExclusive || exclusive);
            }
        }

    }

	public String getPath() {
		return fPath;
	}

	public boolean isExclusive() {
		return fExclusive;
	}

	public boolean isShared() {
		return !fExclusive;
	}
	
	

}